
#include <assert.h>
#include <gfx/image.h>
#include <gfx/gl20/device.h>
#include <gfx/gl20/texture.h>
#include <stdio.h>

namespace
{

unsigned get_max_anisotropic_filter ()
{
  static unsigned max = 0;

  if (max == 0)
  {
    float v;
    glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &v);
    max = (unsigned) v;
  }

  return max;
}

GLenum convert_texture_format (cePixelFormat format)
{
  switch (format)
    {
    case PF_R8G8B8:
      return GL_RGB;
    case PF_R8G8B8A8:
      return GL_RGBA;
    case PF_D24:
      return GL_DEPTH_COMPONENT;
    case PF_D24S8:
      return GL_DEPTH24_STENCIL8;
    default:
      assert (false);
      break;
    }
  return GL_RGB;
}

GLenum convert_texture_data_format (cePixelFormat format)
{
  switch (format)
    {
    case PF_R8G8B8:
      return GL_RGB;
    case PF_R8G8B8A8:
      return GL_RGBA;
    case PF_D24:
      return GL_DEPTH_COMPONENT;
    case PF_D24S8:
      return GL_DEPTH_STENCIL;
    default:
      assert (false);
      break;
    }
  return GL_RGB;
}

GLenum convert_texture_data_type (cePixelFormat format)
{
  switch (format)
    {
    case PF_R8G8B8:
    case PF_R8G8B8A8:
      return GL_UNSIGNED_BYTE;
    case PF_D24:
      return GL_FLOAT;
    case PF_D24S8:
      return GL_UNSIGNED_INT_24_8;
    default:
      assert (false);
      break;
    }
  return GL_UNSIGNED_BYTE;

}

GLenum convert_wrap_mode (ceWrapMode mode)
{
  switch (mode)
    {
    case WM_Repeat:
      return GL_REPEAT;

    case WM_Clamp:
      return GL_CLAMP;

    case WM_ClampToEdge:
      return GL_CLAMP_TO_EDGE;

    case WM_ClampToBorder:
      return GL_CLAMP_TO_BORDER;

    default:
      assert (false);
    }

  return GL_REPEAT;
}

}

ceTexture2D::ceTexture2D ()
	: iTexture2D()
	, _name (0)
{
  CE_OBJECT_CONSTR
}

void ceTexture2D::Bind ()
{
  glEnable (GL_TEXTURE_2D);
  glBindTexture (GL_TEXTURE_2D, _name);
  CE_CHECK_GL_ERROR;
}

bool ceTexture2D::Convert(const ceTextureDescriptor &desc)
{
  return Create (desc);
}

bool ceTexture2D::Create (const ceTextureDescriptor& desc)
{
  CE_CHECK_GL_ERROR;
  if (!_name)
		{
			glGenTextures (1, &_name);
			if (!_name)
				{
					return false;
				}
		}
  _size = desc.size;
  glBindTexture (GL_TEXTURE_2D, _name);
  CE_CHECK_GL_ERROR;

  CE_CHECK_GL_ERROR;

  if (desc.format == PF_D24 || desc.format == PF_D24S8)
		{
			glTexParameteri (GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

      _mipMap = false;
      _anisotropic = 1;
      _filter = F_Nearest;
      _wrapMode = WM_ClampToEdge;
		}
	else
		{
      _mipMap = desc.mipMap;
      _filter = desc.filter;
      _anisotropic = desc.anisotropic;
			_wrapMode = desc.wrapMode;
      if (!_anisotropic) _anisotropic = 1;
      unsigned max = get_max_anisotropic_filter ();
      if (_anisotropic > max) _anisotropic = max;
		}

  UpdateFilter();
  CE_CHECK_GL_ERROR;


  _internalFormat = convert_texture_format(desc.format);
  if (_mipMap)
    {
      unsigned w = _size.w;
      unsigned h = _size.h;
      unsigned l = 0;
      while (true)
        {
          glTexImage2D (GL_TEXTURE_2D, l, _internalFormat, w, h, 0, convert_texture_data_format(desc.format), convert_texture_data_type(desc.format), 0);
          CE_CHECK_GL_ERROR;
          if (w == 1 && h == 1) break;
          l++;
          if (w > 1) w >>= 1;
          if (h > 1) h >>= 1;
        }
    }
  else
    {
      glTexImage2D (GL_TEXTURE_2D, 0, _internalFormat, _size.w, _size.h, 0, convert_texture_data_format(desc.format), convert_texture_data_type(desc.format), 0);
      CE_CHECK_GL_ERROR;
    }
  glBindTexture(GL_TEXTURE_2D, 0);
  CE_CHECK_GL_ERROR;
  return true;
}


void ceTexture2D::UpdateFilter ()
{
  if (_anisotropic > 1 && _filter == F_Anisotropic)
    {
      _mipMap = true;
    }
  else
    {
      _anisotropic = 1;
    }

  GLenum min_filter = GL_NEAREST;
  GLenum mag_filter = GL_NEAREST;

  switch (_filter)
    {
    case F_Nearest:
      min_filter = _mipMap ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
      mag_filter = GL_NEAREST;
      break;

    case F_Bilinear:
      min_filter = _mipMap ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR;
      mag_filter = GL_LINEAR;
      break;

    case F_Trilinear:
      _mipMap = true;
      min_filter = GL_LINEAR_MIPMAP_LINEAR;
      mag_filter = GL_LINEAR;
      break;

    case F_Anisotropic:
      min_filter = GL_LINEAR_MIPMAP_LINEAR;
      mag_filter = GL_LINEAR;
      break;

    }


	GLenum wrap = GL_REPEAT;
	switch (_wrapMode)
		{
		case WM_Repeat:
			wrap = GL_REPEAT;
			break;

		case WM_Clamp:
			wrap = GL_CLAMP;
			break;

		case WM_ClampToEdge:
			wrap = GL_CLAMP_TO_EDGE;
			break;

		case WM_ClampToBorder:
			wrap = GL_CLAMP_TO_BORDER;
			break;

		}

	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
  glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)_anisotropic);

}


void ceTexture2D::SetMipMap (bool mipMap)
{
  assert(_name);


  _mipMap = mipMap;
  UpdateFilter ();
}

bool ceTexture2D::IsMipMap () const
{
  return _mipMap;
}

void ceTexture2D::SetAnisotropic (unsigned anisotropic)
{
  if (!anisotropic) anisotropic = 1;
  unsigned max = get_max_anisotropic_filter ();
  if (anisotropic > max) anisotropic = max;


  _anisotropic = anisotropic;

  UpdateFilter();
}

unsigned ceTexture2D::GetAnisotropic () const
{
  return _anisotropic;
}

void ceTexture2D::SetWrapMode(ceWrapMode wrapMode)
{
  GLenum wm = convert_wrap_mode (wrapMode);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wm);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wm);
  _wrapMode = wrapMode;
}

ceWrapMode ceTexture2D::GetWrapMode() const
{
  return _wrapMode;
}

void ceTexture2D::SetFilter(ceFilter filter)
{
  _filter = filter;
  UpdateFilter();
}

ceFilter ceTexture2D::GetFilter() const
{
  return _filter;
}

void ceTexture2D::SetData(cePixelFormat format, void *data)
{
  CE_CHECK_GL_ERROR;

  assert (_name);
  glBindTexture(GL_TEXTURE_2D, _name);
  if (_mipMap)
    {

      ceImage* image = new ceImage ();
      ceImage::Description desc;
      desc.format = format;
      desc.width = _size.w;
      desc.height = _size.h;
      image->Initialize(desc, data);
      image->GenerateMipMaps();

      for (unsigned l=0; l<image->GetNumberOfLayers(); l++)
        {
          image->SetCurrentLayer(l);

          unsigned width = image->GetWidth();
          unsigned height = image->GetHeight();
          const void* ildata = image->GetData();
          CE_CHECK_GL_ERROR;

          glTexImage2D (GL_TEXTURE_2D,
                        l,
                        _internalFormat,
                        width,
                        height,
                        0,
                        convert_texture_data_format(format),
                        convert_texture_data_type(format),
                        ildata);
          CE_CHECK_GL_ERROR;

        }
      image->Release();
    }
  else
    {
      glTexImage2D (GL_TEXTURE_2D,
                    0,
                    _internalFormat,
                    _size.w,
                    _size.h,
                    0,
                    convert_texture_data_format(format),
                    convert_texture_data_type(format),
                    data);
    }
  CE_CHECK_GL_ERROR;
}

void ceTexture2D::CopyData (unsigned level, const ceRect &rect, cePixelFormat format, const void *data)
{
  assert(_name);
  glBindTexture (GL_TEXTURE_2D, _name);
  glTexSubImage2D (GL_TEXTURE_2D, level, rect.x, rect.y, rect.w, rect.h, convert_texture_format (format),
                   GL_UNSIGNED_BYTE, data);

  CE_CHECK_GL_ERROR;

}
